﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SurfaceCardsBoardLibrary;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Windows;
using SurfaceCardsBoardLibrary.Interfaces;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Controls.Primitives;
using Microsoft.Surface.Presentation;
using System.Windows.Input;
using SurfaceCardsGameKit.Tools;

namespace SurfaceCardsGameKit
{
    public class Game : DependencyObject, IDisposable
    {

        protected class DeckWaitingInitialization
        {
            public Deck Deck { get; set; }
            public List<ICard> Cards { get; set; }
        }

        protected Board _board;
        private List<Card> _cardsWaitingInitializing;
        private List<DeckWaitingInitialization> _decksWaitingInitializing;
        private List<CardDeck> _decksWaitingInitializingAndContactCapure;

        public Game(Board board)
        {
            _board = board;

            CircularMenuWidth = 200;
            CircularMenuHeight = 200;

            this.ManipulableObjects = new ObservableCollection<IManipulableObject>();
            this._cardsWaitingInitializing = new List<Card>();
            this._decksWaitingInitializing = new List<DeckWaitingInitialization>();
            this._decksWaitingInitializingAndContactCapure = new List<CardDeck>();

            this.GameBackground = Application.Current.FindResource("TapisJeuLibre") as DrawingBrush;
            OffsetToCreateDeck = 40;

            this.InitBoard();

            Contacts.AddContactDownHandler(this._board.Parent, new ContactEventHandler(OnBoardContactDown));
            Contacts.AddContactUpHandler(this._board.Parent, new ContactEventHandler(OnBoardContactUp));
            Contacts.AddContactChangedHandler(this._board.Parent, new ContactEventHandler(OnBoardContactChanged));
        }

        #region Properties

        public ObservableCollection<IManipulableObject> ManipulableObjects { get; private set; }

        public DrawingBrush GameBackground { get; set; }

        public double CardWidth { get; set; }
        public double CardHeight { get; set; }

        public double DeckWidth { get; set; }
        public double DeckHeight { get; set; }

        public double CircularMenuHeight { get; set; }
        public double CircularMenuWidth { get; set; }

        public Style CardScatterViewItemStyle { get; set; }
        public Style DeckScatterViewItemStyle { get; set; }
        public Style CircularMenuScatterViewItemStyle { get; set; }

        public double OffsetToCreateDeck { get; set; }

        protected IEnumerable<Deck> Decks
        {
            get { return ManipulableObjects.OfType<Deck>(); }
        }

        protected IEnumerable<Card> Cards
        {
            get { return ManipulableObjects.OfType<Card>(); }
        }

        #endregion

        protected virtual void InitBoard()
        {
            _board.ScatterView.ItemsSource = this.ManipulableObjects;
            _board.AddHandler(Board.ScatterViewItemManipulatedEvent, new ScatterViewItemManipulationEventHandler(OnScatterViewItemManipulated));
            _board.AddHandler(Board.ManipulableObjectStateChangingEvent, new ManipulableObjectStateChangingEventHandler(OnManipulableObjectStateChanging));
            _board.ScatterView.ItemContainerGenerator.StatusChanged += BoardItemContainerGenerator_StatusChanged;
        }

        #region Cards

        public virtual void AddCard(Card card)
        {
            _cardsWaitingInitializing.Add(card);
            this.ManipulableObjects.Add(card);
        }

        protected virtual void InitializeCard(Card card)
        {
            ScatterViewItem svi = _board.ScatterView.GetScatterViewItemForItem(card);

            svi.Style = CardScatterViewItemStyle;
            svi.Width = CardWidth;
            svi.Height = CardHeight;

            var UC = new CardUC();
            svi.Content = UC;

            card.Board = _board;
            card.BeginFlipAnimation = UC.BeginFlipAnimation;
            card.OpacityMaskPoint = UC.SetOpacityMaskPoint;
            card.CancelOpacityMask = UC.CancelOpacityMask;

            _cardsWaitingInitializing.Remove(card);
        }

        #endregion

        #region Decks

        public virtual Deck CreateDeck()
        {
            return CreateDeck(null);
        }

        private void SetDeckDefaultValues(Deck deck)
        {
            SetDeckDefaultValues(deck, new Point(double.NaN, double.NaN));
        }

        private void SetDeckDefaultValues(Deck deck, Point center)
        {
            deck.DefaultHeight = this.DeckHeight;
            deck.DefaultWidth = this.DeckWidth;
            deck.Center = center;
            deck.DefaultCardSize = new Size(this.CardWidth, this.CardHeight);
            deck.SortComparison = (ICard a, ICard b) =>
            {
                //return ((BasicCardValue)a.CardValue).Number - (int)b.CardValue;
                return 0;
            };
            deck.Game = this;
        }

        public virtual Deck CreateDeck(IEnumerable<ICard> cards)
        {
            return CreateDeck(cards, new Point(double.NaN, double.NaN));
        }

        public virtual Deck CreateDeck(IEnumerable<ICard> cards, Point center)
        {
            var deck = new Deck();
            SetDeckDefaultValues(deck, center);

            // throw event first to ask if we can create a deck
            bool ret = _board.RaiseDeckCreatingEvent(deck, cards, center);

            if (ret)
            {
                _decksWaitingInitializing.Add(new DeckWaitingInitialization { Deck = deck, Cards = cards == null ? null : cards.ToList() });
                this.ManipulableObjects.Add(deck);

                return deck;
            }
            else
                return null;
        }

        protected virtual void InitializeDeck(DeckWaitingInitialization deck)
        {
            ScatterViewItem svi = _board.ScatterView.GetScatterViewItemForItem(deck.Deck);

            svi.Style = DeckScatterViewItemStyle;
            svi.Width = DeckWidth;
            svi.Height = DeckHeight;

            var UC = new DeckUC();
            svi.Content = UC;

            deck.Deck.GetCardsDestinationRect = UC.GetCardsDestinationRect;
            deck.Deck.Board = _board;

            // add the cards
            if (deck.Cards != null)
                _board.AddCardsToDeck(deck.Deck, deck.Cards);

            _decksWaitingInitializing.Remove(deck);
        }

        #endregion

        #region Circular Menus

        public virtual CircularMenuUC CreateCircularMenu()
        {
            var menu = new CircularMenuUC();

            this.SetCircularMenuDefaultValues(menu);
            this.ManipulableObjects.Add(menu);

            return menu;
        }

        private void SetCircularMenuDefaultValues(CircularMenuUC menu)
        {
            menu.DefaultWidth = this.CircularMenuWidth;
            menu.DefaultHeight = this.CircularMenuHeight;
            menu.CanMove = true;
            menu.CanScale = true;
            menu.CanRotate = true;

            menu.Style = CircularMenuScatterViewItemStyle;
            menu.Width = CircularMenuWidth;
            menu.Height = CircularMenuHeight;
            menu.Board = _board;
        }

        #endregion

        #region Menu Events
        private CircularMenuUC menu_uc = null;
        private void OnBoardContactDown(object sender, ContactEventArgs args)
        {
            if (args.Contact.IsTagRecognized)
            {
                var circularMenus = this.ManipulableObjects.OfType<CircularMenuUC>();
                if (!circularMenus.Any())
                {
                    CircularMenuUC menu = this.CreateCircularMenu();

                    menu.Orientation = args.Contact.GetOrientation(_board);
                    menu.ListBox.SelectionChanged += CircularMenu_SelectionChanged;
                    menu.Center = args.Contact.GetPosition(_board);

                    Contacts.CaptureContact(args.Contact, this._board.Parent as FrameworkElement, System.Windows.Input.CaptureMode.Element);
                }
                else
                {
                    var menu = circularMenus.First();
                    DispositionHelper.BeginCenterAnimation(menu, args.Contact.GetPosition(_board), new Duration(TimeSpan.FromSeconds(1)));
                    DispositionHelper.BeginOrientationAnimation(menu, args.Contact.GetOrientation(_board), new Duration(TimeSpan.FromSeconds(1)));
                }
            } 
        }

        private void CircularMenu_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            SurfaceListBox s = sender as SurfaceListBox;

            if (s == null)
                return;

            FrameworkElement fe = s.SelectedItem as FrameworkElement;

            if (fe == null)
                return;

            switch (fe.Name)
            {
                case "ChangeGame":
                    if (this.ChangeGameCommand != null)
                    {
                        if (menu_uc != null)
                            menu_uc.ListBox.SelectionChanged -= CircularMenu_SelectionChanged;
                        this.ChangeGameCommand.Execute(null);
                    }
                    break;

                case "Help":
                    if (this.HelpCommand != null)
                    {
                        this.HelpCommand.Execute(null);
                    }
                    break;

                case "CollectCards":
                    if (this.GatherCardsCommand != null)
                        this.GatherCardsCommand.Execute(null);
                    break;
            }

            // remove selection to enable re-selection
            s.SelectedIndex = -1;
        }

        private void OnBoardContactChanged(object sender, ContactEventArgs args)
        {
            if (args.Contact.IsTagRecognized)
            {
                var circularMenus = this.ManipulableObjects.OfType<CircularMenuUC>();
                if (circularMenus.Any())
                {
                    var menu = circularMenus.First();
                    menu.Center = args.Contact.GetPosition(_board);
                }
            }
        }

        private void OnBoardContactUp(object sender, ContactEventArgs args)
        {
            if (args.Contact.IsTagRecognized)
            {
                var circularMenus = this.ManipulableObjects.OfType<CircularMenuUC>();
                if (circularMenus.Any())
                {
                    foreach (var menu in circularMenus.ToArray())
                    {
                        this.ManipulableObjects.Remove(menu);
                    }
                }
            }
        }
        #endregion

        private bool CheckLastContactOnSVI(Point lastPoint, Point actualPoint)
        {
            return Math.Abs(lastPoint.X - actualPoint.X) < OffsetToCreateDeck &&
                Math.Abs(lastPoint.Y - actualPoint.Y) < OffsetToCreateDeck;
        }

        private void OnScatterViewItemManipulated(object source, ScatterViewItemManipulationRoutedEventArgs e)
        {
            var sviSource = (ScatterViewItem)e.OriginalSource;

            if (e.Decks.Any() && sviSource.IsDeck() && e.Decks.First().ToDeck().CanMergeByUser)
            {
                // sliding a deck on another deck (and maybe free cards)
                // merge all decks and free cards into first underlaying deck

                var firstDeck = e.Decks.First().ToDeck();

                if (sviSource.IsAnyContactCaptured || CheckLastContactOnSVI(sviSource.ToDeck().LastCenterBeforeContactsUp, sviSource.ActualCenter))
                {
                    var allOtherDecks =
                    e.Decks.ToDecks()
                    .Except(new IDeck[] { firstDeck })
                    .Union(new IDeck[] { sviSource.ToDeck() });

                    var allCardsFree = e.Cards.ToCards();

                    if ((firstDeck as Deck).CanAddByUser)
                        _board.MergeDecksAndCards(firstDeck, allOtherDecks, allCardsFree);
                }

            }
            else if (sviSource.IsCard() && !e.Decks.Any() && e.Cards.Any() && e.Cards.First().ToCard().CanMergeByUser)
            {
                // sliding a card on some other cards
                // create a deck

                // only merge if the aciton is recent
                if (sviSource.IsAnyContactCaptured || CheckLastContactOnSVI(sviSource.ToCard().LastCenterBeforeContactsUp, sviSource.ActualCenter))
                {
                    var deck = CreateDeck(e.Cards.ToCards().Union(new ICard[] { sviSource.ToCard() }), e.Center);

                    if (deck != null)
                        deck.UpdateDisposition(true, true);
                }
            }
            else if (sviSource.IsCard() && e.Decks.Any())
            {
                // sliding a card on a deck
                // add only the source card on the deck

                var deck = e.Decks.First().ToDeck();

                // only merge if the aciton is recent
                if (sviSource.IsAnyContactCaptured || CheckLastContactOnSVI(sviSource.ToCard().LastCenterBeforeContactsUp, sviSource.ActualCenter))
                {
                    if ((deck as Deck).CanAddByUser)
                        _board.AddCardToDeck(deck, sviSource.ToCard());
                }
            }
            else if (sviSource.IsDeck() && !e.Decks.Any() && e.Cards.Any())
            {
                // sliding a deck on some cards
                // Add all cards to the deck

                var deck = sviSource.ToDeck();

                // only merge if the aciton is recent
                if (sviSource.IsAnyContactCaptured || CheckLastContactOnSVI(sviSource.ToDeck().LastCenterBeforeContactsUp, sviSource.ActualCenter))
                {
                    if ((deck as Deck).CanAddByUser)
                        _board.AddCardsToDeck(deck, e.Cards.ToCards());

                }
            }
            else if ((!e.Cards.Any() && !e.Decks.Any())
                || ((e.Decks.Any() && !e.Decks.First().ToDeck().CanMergeByUser)
                    || (e.Cards.Any() && !e.Cards.First().ToCard().CanMergeByUser)))
            {
                // if sliding a card or a deck on the board
                // raise the event
                _board.RaiseDeckOrCardSlidedOnBoard(sviSource.ToDeck(), sviSource.ToCard());

            }
            else if (sviSource.IsDeck() || sviSource.IsCard())
            {
                throw new Exception("All types of manipulation should be catched");
            }
        }

        protected virtual void OnManipulableObjectStateChanging(object sender, ManipulableObjectStateChangingEventArgs e)
        {
            if (e.ManipulableObjectAction == ManipulableObjectAction.RemoveCardsFromDeck)
            {
                if (e.Cards.Any(c => c.IsSelected))
                {
                    e.Cancel = true;

                    // just don't remove it, but create a deck with all the selected cards from the deck also
                    var oldDeck = (Deck)e.ManipulableSource;
                    var firstCard = e.Cards.First();
                    var cards = e.Cards.Union(
                        oldDeck.Where(c => c.IsSelected)).ToList();

                    // remove all those cards from the deck
                    foreach (var card in cards)
                        card.IsSelected = false;

                    _board.RemoveCardsFromDeck(oldDeck, cards);

                    // create the deck
                    var newDeck = new Deck();
                    SetDeckDefaultValues(newDeck);

                    // throw event first to ask if we can create a deck
                    bool ret = _board.RaiseDeckCreatingEvent(newDeck, cards);

                    if (ret)
                    {
                        _decksWaitingInitializing.Add(new DeckWaitingInitialization { Deck = newDeck, Cards = cards == null ? null : cards.ToList() });
                        // pend capture contact on the scatterviewitem
                        _decksWaitingInitializingAndContactCapure.Add(new CardDeck { Card = firstCard, Deck = newDeck });
                        this.ManipulableObjects.Add(newDeck);

                        if (cards != null)
                            _board.AddCardsToDeck(newDeck, cards);
                    }
                    else
                        return;
                }
            }
        }

        protected virtual void InitializeDeckAndCaptureContact(CardDeck cardDeck)
        {
            // take all contacts from card and put them on deck
            var contacts = Contacts.GetContactsCaptured(cardDeck.Card.ScatterViewItem);
            foreach (var c in contacts)
            {
                cardDeck.Card.ScatterViewItem.CancelManipulation();
                c.Capture(cardDeck.Deck.ScatterViewItem);
            }

            cardDeck.Deck.Center = cardDeck.Card.ScatterViewItem.ActualCenter;
            if (contacts.Any())
                cardDeck.Deck.Orientation = contacts.First().GetOrientation(this._board.ScatterView) - 90;

            _decksWaitingInitializingAndContactCapure.Remove(cardDeck);
        }

        private void BoardItemContainerGenerator_StatusChanged(object sender, EventArgs e)
        {
            if (_board.ScatterView.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                // initialize all cards waiting for init
                foreach (Card card in _cardsWaitingInitializing.Intersect(Cards).ToList())
                    InitializeCard(card);

                // initialize all deck waiting for init
                foreach (DeckWaitingInitialization deck in _decksWaitingInitializing.Where(dw => Decks.Contains(dw.Deck)).ToList())
                    InitializeDeck(deck);

                foreach (CardDeck cardDeck in _decksWaitingInitializingAndContactCapure.Where(cd => Decks.Contains(cd.Deck)).ToList())
                    InitializeDeckAndCaptureContact(cardDeck);
            }
        }

        protected class CardDeck
        {
            public Deck Deck { get; set; }
            public ICard Card { get; set; }
        }


        #region Commands

        public ICommand ChangeGameCommand { get; set; }
        public virtual ICommand GatherCardsCommand { get; set; }

        private DelegateCommand _helpCommand;
        public ICommand HelpCommand
        {
            get
            {
                if (_helpCommand == null)
                {
                    _helpCommand = new DelegateCommand(
                    () =>
                    {
                        if (!this.ManipulableObjects.OfType<Games.BasicGame.HelpBook>().Any())
                        {
                            var helpBook = new Games.BasicGame.HelpBook();
                            helpBook.CloseBookCommand = CloseBookCommand;
                            this.ManipulableObjects.Add(helpBook);
                        }
                    });
                }
                return _helpCommand;
            }
        }

        private DelegateCommand _closeBookCommand;
        public ICommand CloseBookCommand
        {
            get
            {
                if (_closeBookCommand == null)
                {
                    _closeBookCommand = new DelegateCommand(
                    () => 
                    {
                        var book = this.ManipulableObjects.OfType<Games.BasicGame.HelpBook>().FirstOrDefault();
                        if (book != null)
                            this.ManipulableObjects.Remove(book);
                    });
                }
                return _closeBookCommand;
            }
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            _board.ScatterView.ItemContainerGenerator.StatusChanged -= BoardItemContainerGenerator_StatusChanged;

            _board.RemoveHandler(Board.ScatterViewItemManipulatedEvent, new ScatterViewItemManipulationEventHandler(OnScatterViewItemManipulated));
            _board.RemoveHandler(Board.ManipulableObjectStateChangingEvent, new ManipulableObjectStateChangingEventHandler(OnManipulableObjectStateChanging));

            Contacts.RemoveContactDownHandler(this._board.Parent, new ContactEventHandler(OnBoardContactDown));
            Contacts.RemoveContactUpHandler(this._board.Parent, new ContactEventHandler(OnBoardContactUp));
            Contacts.RemoveContactChangedHandler(this._board.Parent, new ContactEventHandler(OnBoardContactChanged));

            this.ManipulableObjects.Clear(); 

            OnDispose();
        }

        protected virtual void OnDispose()
        {
        }

        #endregion
    }
}
